package com.abel.bigwater.data.sanchuan

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.data.InvalidPacketException
import com.abel.bigwater.data.NotEnoughDataException
import com.abel.bigwater.data.UnknownPacketException
import io.netty.buffer.ByteBuf
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.slf4j.LoggerFactory
import java.util.*
import kotlin.experimental.and

class SanchuanPacket {
    var backArray: ByteArray? = null

//    val flag1 = 0x68

    /**
     * LEN-H	帧长高字节【L=10+length（DATA）】
    LEN-L	帧长低字节【从CTRLB开始到DATA止】
     */
    var len: Int = 10

//    val flag2 = 0x68

    var ctrlb: CtrlbType? = null

    var actrlb: Short? = null

    /**
     * MeterTypeID	表类型ID
    MeterAddr1	表地址1位
    MeterAddr2	表地址2位
    MeterAddr3	表地址3位
    MeterAddr4	表地址4位
    MeterAddr5	表地址5位
    FactoryCode1	厂商代码1位
    FactoryCode2	厂商代码2位
     */
    var meterId: BcdString? = null

    var meterIdFmt: String? = null
        get() = "xx-${meterId?.reversedBcdString()}"

    var data: SanchuanData? = null

    var checksum: Short? = null

//    val flag3 = 0x16

    /**
     * 填充字节流
     */
    @Throws(IllegalArgumentException::class, NullPointerException::class)
    fun fillBuffer(buf: ByteBuf) {
        len = 10 + data!!.computeDataLen()

        val tmp = buf.alloc().buffer().apply {
            writeByte(flag1)

            writeShort(len)

            writeByte(flag2)

            writeByte(ctrlb!!.code)

            writeByte(actrlb!!.toInt())

            writeBytes(meterId!!.hexArray)

            data!!.fillBuffer(this)

            // compute check-sum
            checksum = 0
            this.duplicate().skipBytes(4).readBytes(len).forEachByte {
                checksum = (checksum!! + (it.toShort() and 0xFF)).toShort() and 0xFF
                true
            }

            writeByte(checksum!!.toInt())

            writeByte(flag3)
        }

        // 整体写入buf
        buf.writeBytes(tmp)
    }

    companion object {

        const val MIN_LEN = 4 + 10 + 2

        val flag1 = 0x68

        val flag2 = 0x68

        val flag3 = 0x16

        /**
         * 解析字节流
         */
        @Throws(NotEnoughDataException::class, UnknownPacketException::class, InvalidPacketException::class)
        fun parseBuffer(buf: ByteBuf): SanchuanPacket? {
            if (buf.readableBytes() < MIN_LEN) {
                throw NotEnoughDataException("数据明显不足: ${buf.readableBytes()}")
            }

            var len: Int = Int.MIN_VALUE
            buf.duplicate().apply {
                var cnt = 0
                while (readByte().toInt() != flag1) {
                    if (++cnt > 60) throw UnknownPacketException("未找到标识1: 0x68")
                }
                len = readShort().toInt().and(0xFFFF)

                // 先判断 标识2 后判断长度
                readByte().toInt().and(0xFF).also {
                    if (flag2 != it) throw UnknownPacketException("标识2不是68H: ${it.toString(16)}H")
                }

                if (this.readableBytes() < len + 2) {
                    throw NotEnoughDataException("数据不足: ${buf.readableBytes()} < ${4 + len + 2}")
                }

                // CS	校验 =（从CTRLB到DATA累计和）模256
                var cs2 = 0
                readBytes(len).forEachByte {
                    cs2 = (cs2 + it.toShort().and(0xFF)) and 0xFF
                    true
                }
                val cs = readByte().toShort() and 0xFF

                lgr.warn("读取CS:${cs.toString(16)}H =? 计算CS:${cs2.toString(16)}H")

                // check flag3
                readByte().toShort().and(0xFF).also {
                    if (flag3.toShort().and(0xFF) != it) {
                        throw InvalidPacketException("不正确的 标识3: ${it.toString(16)}H")
                    }
                }
            }

            // 保存字节流
            val ba = ByteArray(4 + len + 2).also { buf.duplicate().readBytes(it) }

            // find flag1 ~ flag2
            buf.readBytes(4 + len).apply {
                // skip check-sum & flag3
                buf.skipBytes(2)

                // skip flag1-len-flag2
                skipBytes(4)

                val p1 = SanchuanPacket().also {
                    it.backArray = ba

                    it.len = len

                    // 控制码
                    val ctrlbCode = readByte().toInt().and(0xFF)
                    it.ctrlb = CtrlbType.findByCode(ctrlbCode)
                    if (it.ctrlb == null) {
                        val msg = "不支持的控制码: ${ctrlbCode.toString(16)}H"
                        lgr.error(msg)

                        throw UnknownPacketException(msg)
                    }

                    it.actrlb = readByte().toShort() and 0xFF

                    // 表地址
                    it.meterId = BcdString.create(this, 8)

                    it.data = when (it.ctrlb) {
                        CtrlbType.GprsOnline -> {
                            GprsOnlineData(it.len - 10).parseBuffer(this)
                        }

                        CtrlbType.Online -> {
                            MeterOnlineData(it.len - 10).parseBuffer(this)
                        }

                        CtrlbType.OnlineReply ->
                            MeterOnlineReply(it.len - 10).parseBuffer(this)

                        CtrlbType.Offline -> {
                            MeterOfflineData(it.len - 10).parseBuffer(this)
                        }

                        CtrlbType.OfflineReply -> {
                            MeterOfflineReply(it.len - 10).parseBuffer(this)
                        }

                        CtrlbType.FetchDetail -> {
                            FetchData(it.len - 10).parseBuffer<FetchData>(this)
                        }

                        CtrlbType.FetchDetailReply -> {
                            when (duplicate().readByte().toShort().and(0xFF)) {
                                FetchCmdType.MonthBill.code -> FetchDataReplyMonthBill(it.len - 10)
                                        .parseBuffer<FetchDataReplyMonthBill>(this)

                                FetchCmdType.DayBill.code -> FetchDataReplyDayBill(it.len - 10)
                                        .parseBuffer<FetchDataReplyDayBill>(this)

                                else -> FetchDataReplyWeekDetail(it.len - 10)
                                        .parseBuffer<FetchDataReplyWeekDetail>(this)
                            }
                        }

                        else -> {
                            throw UnknownPacketException("不支持的控制码: ${it.ctrlb}")
                        }
                    }
                }

                return p1
            }

            // ingore
            return null
        }

        private val lgr = LoggerFactory.getLogger(SanchuanPacket::class.java)
    }
}

/**
 * 7	YYYYMMDDHHMMSS 	年年月日时分秒（采用BCD码）。格式参考参数表，参数编码110C
 */
fun BcdString.toSanchunDate(): Date? {
    if (this.size != 7) throw IllegalArgumentException("sanchuan-时间应为7位长度: ${this.size}")

    return DateTime(bcdArray!![0].toInt().and(0xFF) * 100 + bcdArray!![1].toInt().and(0xFF),
            bcdArray!![2].toInt().and(0xFF), bcdArray!![3].toInt().and(0xFF),
            bcdArray!![4].toInt().and(0xFF), bcdArray!![5].toInt().and(0xFF),
            bcdArray!![6].toInt().and(0xFF)).toDate()
}

/**
 * 7	YYYYMMDDHHMMSS 	年年月日时分秒（采用BCD码）。格式参考参数表，参数编码110C
 */
fun BcdString.Companion.fromSanchuanDate(time: Date): BcdString {
    val dt = DateTime(time)
    return BcdString(byteArrayOf((dt.year / 100).toByte(), (dt.year % 100).toByte(),
            dt.monthOfYear.toByte(), dt.dayOfMonth.toByte(),
            dt.hourOfDay.toByte(), dt.minuteOfHour.toByte(),
            dt.secondOfMinute.toByte()), true)
}

/**
 * 5 密集采样起始时间 5 字节（采用 BCD 码），年月日时分。 禁止上报密集采样流量时仍上报密集采样起始时间
 */
fun BcdString.toStartTime(): DateTime? {
    if (this.size != 5) throw  IllegalArgumentException("sanchuan-start-time时间应为5字节")

    return DateTime(DateTime.now().year / 100 + bcdArray!![0].toInt(),
            bcdArray!![1].toInt(), bcdArray!![2].toInt(), bcdArray!![3].toInt(), bcdArray!![4].toInt())
}

/**
 * 5 密集采样起始时间 5 字节（采用 BCD 码），年月日时分。 禁止上报密集采样流量时仍上报密集采样起始时间
 */
fun BcdString.Companion.fromStartTime(dt: DateTime): BcdString {
    return BcdString(byteArrayOf((dt.year % 100).toByte(),
            dt.monthOfYear.toByte(), dt.dayOfMonth.toByte(),
            dt.hourOfDay.toByte(), dt.minuteOfHour.toByte()),
            true)
}


/**
 * 单位: m³.
 * 5 当前累计流量 格式见参数表，参数编码 1110
 * 累计流量
格式 2 11 10 5
16 进制无符号低字节在前，单位 0.1L。
例： 0x00,0x01,0x00,0x00,0x00 表示当前累计流量为： 25.6L
表示水表从激活后到当前时间累计流过的总水量。注意与参数编码(0101)格式不
一致。
 */
fun BcdString.toUlongLEHex(size: Int = 5): Long? {
    if (this.size != size || this.size > 8) throw IllegalArgumentException("unsigned long应为${size}字节: ${this.size}")

    var r1: Long = 0
    for (b1 in hexArray!!.reversedArray()) {
        r1 = 256 * r1 + b1.toLong().and(0xFF)
    }

    return r1
}

fun BcdString.Companion.fromUlongLEHex(r1: Long?, size: Int = 5): BcdString? {
    if (size > 8) throw IllegalArgumentException("unsigned long不能超过8字节: ${size}")
    r1 ?: return null

    val hex = ByteArray(size)
    var r2 = r1
    for (idx in 0..(size - 1)) {
        hex[idx] = (r2 % 256).toByte()
        r2 /= 256
    }

    return BcdString(hex, false)
}

enum class CtrlbType(_code: Int) {
    MeterParamReply(0x01),
    MeterParam(0x81),

    MeterAddr(0x82),
    MeterAddrReply(0x02),

    GprsOnline(0x10),

    Online(0x11),

    OnlineReply(0x90),

    Offline(0xF9),

    OfflineReply(0x79),

    FetchDetail(0x92),
    FetchDetailReply(0x12);

    val code: Int = _code

    companion object {
        fun findByCode(_code: Int): CtrlbType? = CtrlbType.values().find { it.code == _code }
    }
}